using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class CameraHelper : LineSegments
    {
        #region scope properties or methods
        //private static Vector3 _vector = new Vector3();
        //private static Camera _camera = new Camera();
        public static CameraHelperContext getContext()
        {
            return ThreeThreadContext.GetCurrThreadContext().CameraHelperCtx;
        }
        #endregion

        #region Properties

        public Camera camera;
        public JsObj<ListEx<int>> pointMap;

        #endregion

        #region constructor
        private CameraHelper(Tuple<BufferGeometry, JsObj<ListEx<int>>> result)
           : base(result.Item1, new LineBasicMaterial{ color = new Color(0xffffff), vertexColors = true, toneMapped = false })
        {
            this.pointMap = result.Item2;
        }
        public CameraHelper(Camera camera)
            : this(makeGeometry())
        {
            this.type = "CameraHelper";
            this.camera = camera;
            this.camera.updateProjectionMatrix();

            this.matrix = camera.matrixWorld;
            this.matrixAutoUpdate = false;
            this.update();
            // colors
            var colorFrustum = new Color(0xffaa00);
            var colorCone = new Color(0xff0000);
            var colorUp = new Color(0x00aaff);
            var colorTarget = new Color(0xffffff);
            var colorCross = new Color(0x333333);
            this.setColors(colorFrustum, colorCone, colorUp, colorTarget, colorCross);
        }

        private static Tuple<BufferGeometry, JsObj<ListEx<int>>> makeGeometry()
        {
            var geometry = new BufferGeometry();
            var material = new LineBasicMaterial{ color = new Color(0xffffff), vertexColors = true, toneMapped = false };
            var vertices = new ListEx<double>();
            var colors = new ListEx<double>();
            var pointMap = new JsObj<ListEx<int>>();
            void addLine(string a, string b)
            {
                addPoint(a);
                addPoint(b);
            }
            void addPoint(string id)
            {
                vertices.Push(0, 0, 0);
                colors.Push(0, 0, 0);
                if (pointMap[id] == null)
                {
                    pointMap[id] = new ListEx<int>();
                }
                pointMap[id].Push((vertices.Length / 3) - 1);
            }
            // near
            addLine("n1", "n2");
            addLine("n2", "n4");
            addLine("n4", "n3");
            addLine("n3", "n1");
            // far
            addLine("f1", "f2");
            addLine("f2", "f4");
            addLine("f4", "f3");
            addLine("f3", "f1");
            // sides
            addLine("n1", "f1");
            addLine("n2", "f2");
            addLine("n3", "f3");
            addLine("n4", "f4");
            // cone
            addLine("p", "n1");
            addLine("p", "n2");
            addLine("p", "n3");
            addLine("p", "n4");
            // up
            addLine("u1", "u2");
            addLine("u2", "u3");
            addLine("u3", "u1");
            // target
            addLine("c", "t");
            addLine("p", "c");
            // cross
            addLine("cn1", "cn2");
            addLine("cn3", "cn4");
            addLine("cf1", "cf2");
            addLine("cf3", "cf4");

            geometry.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            geometry.setAttribute("color", new Float32BufferAttribute(colors.ToArray(), 3));

            return Tuple.Create<BufferGeometry, JsObj<ListEx<int>>>(geometry, pointMap);
        }
        #endregion

        #region methods
        public void setColors(Color frustum, Color cone, Color up, Color target, Color cross)
        {
            var geometry = this.geometry;
            var colorAttribute = geometry.getAttribute("color");
            // near
            colorAttribute.setXYZ(0, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(1, frustum.R, frustum.G, frustum.B); // n1, n2
            colorAttribute.setXYZ(2, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(3, frustum.R, frustum.G, frustum.B); // n2, n4
            colorAttribute.setXYZ(4, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(5, frustum.R, frustum.G, frustum.B); // n4, n3
            colorAttribute.setXYZ(6, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(7, frustum.R, frustum.G, frustum.B); // n3, n1
                                                                                                                                  // far
            colorAttribute.setXYZ(8, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(9, frustum.R, frustum.G, frustum.B); // f1, f2
            colorAttribute.setXYZ(10, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(11, frustum.R, frustum.G, frustum.B); // f2, f4
            colorAttribute.setXYZ(12, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(13, frustum.R, frustum.G, frustum.B); // f4, f3
            colorAttribute.setXYZ(14, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(15, frustum.R, frustum.G, frustum.B); // f3, f1
                                                                                                                                    // sides
            colorAttribute.setXYZ(16, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(17, frustum.R, frustum.G, frustum.B); // n1, f1
            colorAttribute.setXYZ(18, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(19, frustum.R, frustum.G, frustum.B); // n2, f2
            colorAttribute.setXYZ(20, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(21, frustum.R, frustum.G, frustum.B); // n3, f3
            colorAttribute.setXYZ(22, frustum.R, frustum.G, frustum.B); colorAttribute.setXYZ(23, frustum.R, frustum.G, frustum.B); // n4, f4
                                                                                                                                    // cone
            colorAttribute.setXYZ(24, cone.R, cone.G, cone.B); colorAttribute.setXYZ(25, cone.R, cone.G, cone.B); // p, n1
            colorAttribute.setXYZ(26, cone.R, cone.G, cone.B); colorAttribute.setXYZ(27, cone.R, cone.G, cone.B); // p, n2
            colorAttribute.setXYZ(28, cone.R, cone.G, cone.B); colorAttribute.setXYZ(29, cone.R, cone.G, cone.B); // p, n3
            colorAttribute.setXYZ(30, cone.R, cone.G, cone.B); colorAttribute.setXYZ(31, cone.R, cone.G, cone.B); // p, n4
                                                                                                                  // up
            colorAttribute.setXYZ(32, up.R, up.G, up.B); colorAttribute.setXYZ(33, up.R, up.G, up.B); // u1, u2
            colorAttribute.setXYZ(34, up.R, up.G, up.B); colorAttribute.setXYZ(35, up.R, up.G, up.B); // u2, u3
            colorAttribute.setXYZ(36, up.R, up.G, up.B); colorAttribute.setXYZ(37, up.R, up.G, up.B); // u3, u1
                                                                                                      // target
            colorAttribute.setXYZ(38, target.R, target.G, target.B); colorAttribute.setXYZ(39, target.R, target.G, target.B); // c, t
            colorAttribute.setXYZ(40, cross.R, cross.G, cross.B); colorAttribute.setXYZ(41, cross.R, cross.G, cross.B); // p, c
                                                                                                                        // cross
            colorAttribute.setXYZ(42, cross.R, cross.G, cross.B); colorAttribute.setXYZ(43, cross.R, cross.G, cross.B); // cn1, cn2
            colorAttribute.setXYZ(44, cross.R, cross.G, cross.B); colorAttribute.setXYZ(45, cross.R, cross.G, cross.B); // cn3, cn4
            colorAttribute.setXYZ(46, cross.R, cross.G, cross.B); colorAttribute.setXYZ(47, cross.R, cross.G, cross.B); // cf1, cf2
            colorAttribute.setXYZ(48, cross.R, cross.G, cross.B); colorAttribute.setXYZ(49, cross.R, cross.G, cross.B); // cf3, cf4
            colorAttribute.needsUpdate = true;
        }
        public void update()
        {
            var _camera = getContext()._camera;
            var geometry = this.geometry;
            var pointMap = this.pointMap;
            double w = 1, h = 1;
            // we need just camera projection matrix inverse
            // world matrix must be identity
            _camera.projectionMatrixInverse.Copy(this.camera.projectionMatrixInverse);
            // center / target
            setPoint("c", pointMap, geometry, _camera, 0, 0, -1);
            setPoint("t", pointMap, geometry, _camera, 0, 0, 1);
            // near
            setPoint("n1", pointMap, geometry, _camera, -w, -h, -1);
            setPoint("n2", pointMap, geometry, _camera, w, -h, -1);
            setPoint("n3", pointMap, geometry, _camera, -w, h, -1);
            setPoint("n4", pointMap, geometry, _camera, w, h, -1);
            // far
            setPoint("f1", pointMap, geometry, _camera, -w, -h, 1);
            setPoint("f2", pointMap, geometry, _camera, w, -h, 1);
            setPoint("f3", pointMap, geometry, _camera, -w, h, 1);
            setPoint("f4", pointMap, geometry, _camera, w, h, 1);
            // up
            setPoint("u1", pointMap, geometry, _camera, w * 0.7, h * 1.1, -1);
            setPoint("u2", pointMap, geometry, _camera, -w * 0.7, h * 1.1, -1);
            setPoint("u3", pointMap, geometry, _camera, 0, h * 2, -1);
            // cross
            setPoint("cf1", pointMap, geometry, _camera, -w, 0, 1);
            setPoint("cf2", pointMap, geometry, _camera, w, 0, 1);
            setPoint("cf3", pointMap, geometry, _camera, 0, -h, 1);
            setPoint("cf4", pointMap, geometry, _camera, 0, h, 1);
            setPoint("cn1", pointMap, geometry, _camera, -w, 0, -1);
            setPoint("cn2", pointMap, geometry, _camera, w, 0, -1);
            setPoint("cn3", pointMap, geometry, _camera, 0, -h, -1);
            setPoint("cn4", pointMap, geometry, _camera, 0, h, -1);
            geometry.attributes.position.needsUpdate = true;
        }

        private static void setPoint(string point, JsObj<ListEx<int>> pointMap, BufferGeometry geometry, Camera camera, double x, double y, double z)
        {
            var _vector = getContext()._vector;
            _vector.Set(x, y, z).Unproject(camera);
            var points = pointMap[point];

            if (points != null)
            {
                var position = geometry.attributes.position;

                for (int i = 0, l = points.Length; i < l; i++)
                {
                    position.setXYZ(points[i], _vector.X, _vector.Y, _vector.Z);
                }
            }
        }

        public void dispose()
        {
            this.geometry.dispose();
            this.material.dispose();
        }
        #endregion

    }
}
